package cn.codeartist.springboot.component.http.proxy.impl;

import cn.codeartist.springboot.component.http.bean.RequestWrapper;
import cn.codeartist.springboot.component.http.bean.TypeReference;
import cn.codeartist.springboot.component.http.exception.HttpException;
import cn.codeartist.springboot.component.http.proxy.HttpProxy;
import cn.codeartist.springboot.component.http.proxy.HttpProxyEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Optional;

/**
 * 配置RestTemplate代理
 *
 * @author 艾江南
 * @date 2019/3/23
 */
public abstract class AbstractHttpProxy extends HttpProxyEntity implements BeanNameAware, HttpProxy {

    protected static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static final Logger logger = LoggerFactory.getLogger(AbstractHttpProxy.class);

    /**
     * 创建Bean的名称
     */
    protected String beanName;
    /**
     * 多例的RestTemplate实例
     */
    @Autowired
    protected RestTemplate restTemplate;

    /**
     * 初始化Bean，使用多例创建不同的代理
     */
    @PostConstruct
    private void init() {
        logger.debug("{} register with host:{}, header:{}, readTimeout:{}, connectTimeout:{}", beanName, host, header, readTimeout, connectTimeout);
        if (StringUtils.isEmpty(host)) {
            throw new BeanCreationException("Error creating bean with name '" + beanName + "': host must not be null");
        }
        // 根据配置重置超时时间
        ClientHttpRequestFactory factory = restTemplate.getRequestFactory();
        if (factory instanceof SimpleClientHttpRequestFactory) {
            SimpleClientHttpRequestFactory requestFactory = (SimpleClientHttpRequestFactory) factory;
            Optional.ofNullable(readTimeout).ifPresent(requestFactory::setReadTimeout);
            Optional.ofNullable(connectTimeout).ifPresent(requestFactory::setConnectTimeout);
        } else if (factory instanceof HttpComponentsClientHttpRequestFactory) {
            HttpComponentsClientHttpRequestFactory requestFactory = (HttpComponentsClientHttpRequestFactory) factory;
            Optional.ofNullable(readTimeout).ifPresent(requestFactory::setReadTimeout);
            Optional.ofNullable(connectTimeout).ifPresent(requestFactory::setConnectTimeout);
        }
    }

    @Override
    public <T, P> T exchange(HttpMethod method, HttpHeaders headers, String uri, P requestBody, Class<T> responseType, Object... uriVariables) {
        if (method == HttpMethod.GET || method == HttpMethod.DELETE) {
            uri = queryTemplateHandler(uri, requestBody);
            requestBody = null;
        }
        HttpHeaders httpHeaders = headerRequestCallback(null);
        httpHeaders.addAll(headers);
        URI url = restTemplate.getUriTemplateHandler().expand(host + uri, uriVariables);
        return execute(new RequestEntity<>(requestBody, headers, method, url), responseType, null);
    }

    @Override
    public <T, P> T exchange(HttpMethod method, HttpHeaders headers, String uri, P requestBody, TypeReference<T> responseType, Object... uriVariables) {
        if (method == HttpMethod.GET || method == HttpMethod.DELETE) {
            uri = queryTemplateHandler(uri, requestBody);
            requestBody = null;
        }
        HttpHeaders httpHeaders = headerRequestCallback(null);
        httpHeaders.addAll(headers);
        URI url = restTemplate.getUriTemplateHandler().expand(host + uri, uriVariables);
        return execute(new RequestEntity<>(requestBody, headers, method, url), null, responseType);
    }

    /**
     * HttpProxy 封装响应对象类型
     *
     * @param method       请求方法
     * @param uri          请求URI
     * @param mediaType    ContentType
     * @param body         请求实体对象
     * @param responseType 响应对象类型
     * @param uriVariables URI插值
     * @param <T>          响应体泛型
     * @return 响应实体
     */
    protected <T, P> T execute(HttpMethod method, String uri, MediaType mediaType, P body, Class<T> responseType, Object... uriVariables) {
        HttpHeaders headers = headerRequestCallback(mediaType);
        URI url = restTemplate.getUriTemplateHandler().expand(host + uri, uriVariables);
        return execute(new RequestEntity<>(body, headers, method, url), responseType, null);
    }

    /**
     * HttpProxy 封装响应对象组合类型
     *
     * @param method       请求方法
     * @param uri          请求URI
     * @param mediaType    ContentType
     * @param body         请求实体对象
     * @param responseType 响应对象响应对象类型组合类型
     * @param uriVariables URI插值
     * @param <T>          响应体泛型
     * @return 响应实体
     */
    protected <T> T execute(HttpMethod method, String uri, MediaType mediaType, Object body, TypeReference<T> responseType, Object... uriVariables) {
        HttpHeaders headers = headerRequestCallback(mediaType);
        URI url = restTemplate.getUriTemplateHandler().expand(host + uri, uriVariables);
        return execute(new RequestEntity<>(body, headers, method, url), null, responseType);
    }

    /**
     * HttpProxy请求异常处理
     *
     * @param requestEntity       请求实体
     * @param responseType        响应对象类型
     * @param complexResponseType 响应对象组合类型
     * @param <T>                 响应体泛型
     * @return 响应体
     */
    protected <T> T execute(RequestEntity<?> requestEntity, Class<T> responseType, ParameterizedTypeReference<T> complexResponseType) {
        try {
            logger.info("{} http request: {} {}, param:{}, head:{}", beanName, requestEntity.getMethod(), requestEntity.getUrl(), requestEntity.getBody(), requestEntity.getHeaders());
            long start = System.currentTimeMillis();
            T response = doRequest(requestEntity, responseType, complexResponseType);
            long end = System.currentTimeMillis();
            logger.info("{} http response: {} {}, param:{}, body:{}, cost:{}ms", beanName, requestEntity.getMethod(), requestEntity.getUrl(), requestEntity.getBody(), response, end - start);
            return response;
        } catch (HttpClientErrorException | HttpServerErrorException e) {
            logger.error("{} request fail: {} {}, param:{}", beanName, requestEntity.getMethod(), requestEntity.getUrl(), requestEntity.getBody(), e);
            throw new HttpException(e.getStatusCode(), requestEntity.getUrl(), requestEntity.getBody(), e);
        } catch (Exception e) {
            logger.error("{} request fail: {} {}, param:{}", beanName, requestEntity.getMethod(), requestEntity.getUrl(), requestEntity.getBody(), e);
            throw new HttpException(null, requestEntity.getUrl(), requestEntity.getBody(), e);
        }
    }

    /**
     * HttpProxy请求最底层封闭（响应对象类型和组合类型选其一）
     *
     * @param requestEntity       请求实体
     * @param responseType        响应对象类型
     * @param complexResponseType 响应对象组合类型
     * @param <T>                 响应体泛型
     * @return 响应体
     */
    protected <T> T doRequest(RequestEntity<?> requestEntity, Class<T> responseType, ParameterizedTypeReference<T> complexResponseType) {
        ResponseEntity<T> responseEntity = responseType != null ?
                restTemplate.exchange(requestEntity, responseType) :
                restTemplate.exchange(requestEntity, complexResponseType);
        return responseEntity.getBody();
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    /**
     * URI添加查询对象参数
     *
     * @param uri   原始URI
     * @param query GET 参数对象
     * @return URI
     */
    protected String queryTemplateHandler(String uri, Object query) {
        if (uri == null || uri.trim().isEmpty()) {
            throw new NullPointerException(beanName + ": URI is blank");
        }
        String serializeParam = RequestWrapper.serializeParam(query, false);
        if ("".equals(serializeParam)) {
            return uri;
        }
        StringBuilder builder = new StringBuilder(uri);
        if (builder.indexOf(RequestWrapper.QS_SEP_START) < 0) {
            builder.append(RequestWrapper.QS_SEP_START);
        }
        if (!builder.toString().endsWith(RequestWrapper.QS_SEP_START)) {
            builder.append(RequestWrapper.QS_SEP_AND);
        }
        builder.append(serializeParam);
        return builder.toString();
    }

    private HttpHeaders headerRequestCallback(MediaType mediaType) {
        HttpHeaders headers = new HttpHeaders();
        // 添加配置中的固定请求头
        if (!CollectionUtils.isEmpty(header)) {
            logger.debug("add absolute header: {}", header);
            headers.setAll(header);
        }
        // 添加编码和媒体类型请求头
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
        if (mediaType != null) {
            headers.setContentType(mediaType);
        }
        return headers;
    }
}
